package UI;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.ProgressBar;

import Data.Character;
import Data.GameException;
import Data.Player;
import Logic.Board;
import Logic.Board.CellStatus;
import Logic.Game.CurrentPlayerStatus;
import Logic.Game.GameOverStatus;
import Logic.Game;
import Logic.Settings;

/**
 * Class for the main shell, where the game takes place.
 */
public class GameShell extends BaseShell
{
	private Label player1Label;
	private Label player2Label;
	private Label guideLabel;
	
	private Button stopButton;
	
	private Composite boardComposite;
	private Control[][] boardButtons;
	
	private Composite creatureComposite;
	private ProgressBar[][] creatureHealthBars;
	private Button[][] creatureButtons;
	
	private GameStoppedCallback callback;
	private Game game;
	
	private int turnState;
	private Character attacker;
	private Character defender;
	private int attackX;
	private int attackY;
	private int defendX;
	private int defendY;
	
	/**
	 * Creates the game shell. Initializes the UI elements.
	 * 
	 * @param callback The callback to be used when the game is stopped.
	 * @param game An instance of the game to represent in the shell.
	 */
	public GameShell(GameStoppedCallback callback, Game game)
	{
		super();
		
		this.callback = callback;
		this.game = game;
		
		setText("It's Game Time");
		
		////////////// Stop button //////////////////////
		
		stopButton = new Button(this, SWT.PUSH);
		stopButton.setText("Stop Game");
		
		FormData formData = new FormData();
		formData.top = new FormAttachment(0, 0);
		formData.left = new FormAttachment(50, -100);
		formData.width = 200;
		formData.height = 70;
		
		stopButton.setLayoutData(formData);
		stopButton.addSelectionListener(new SelectionAdapter()
		{
			@Override
			public void widgetSelected(SelectionEvent e)
			{
				stopGame();
			}
		});
		
		///////////////////// Player labels ///////////////////
		
		player1Label = new Label(this, SWT.RIGHT);
		
		FontData[] fd = player1Label.getFont().getFontData();
		fd[0].setHeight(16);
		fd[0].setStyle(SWT.BOLD);
		
		player1Label.setFont(new Font(getDisplay(), fd[0]));
		player1Label.setForeground(new Color(SWTUtil.getDisplay(), 0, 0, 255));
		
		formData = new FormData();
		formData.top = new FormAttachment(0, 20);
		formData.right = new FormAttachment(stopButton, -40, SWT.LEAD);
		formData.left = new FormAttachment(0, 0);
		formData.height = 50;
		
		player1Label.setLayoutData(formData);
		
		player2Label = new Label(this, SWT.LEFT);
		
		fd = player2Label.getFont().getFontData();
		fd[0].setHeight(16);
		fd[0].setStyle(SWT.BOLD);
		
		player2Label.setFont(new Font(getDisplay(), fd[0]));
		player2Label.setForeground(new Color(SWTUtil.getDisplay(), 255, 0, 0));
		
		formData = new FormData();
		formData.top = new FormAttachment(0, 20);
		formData.left = new FormAttachment(stopButton, 40, SWT.TRAIL);
		formData.right = new FormAttachment(100, 0);
		formData.height = 50;
		
		player2Label.setLayoutData(formData);
		
		///////////////////// Creatures  //////////////////
		
		creatureComposite = new Composite(this, SWT.NONE);
		creatureComposite.setLayout(new FillLayout());
		
		formData = new FormData();
		formData.left = new FormAttachment(0, 0);
		formData.right = new FormAttachment(100, 0);
		formData.bottom = new FormAttachment(100, 0);
		formData.height = 100;
		
		creatureComposite.setLayoutData(formData);
		
		int armySize = Settings.getArmySize();
		
		creatureHealthBars = new ProgressBar[2][];
		creatureHealthBars[0] = new ProgressBar[armySize];
		creatureHealthBars[1] = new ProgressBar[armySize];
		
		creatureButtons = new Button[2][];
		creatureButtons[0] = new Button[armySize];
		creatureButtons[1] = new Button[armySize];
		
		for (int p = 0; p < 2; p++)
		{
			for (int i = 0; i < armySize; i++)
			{
				Composite composite = new Composite(creatureComposite, SWT.NONE);
				composite.setLayout(new FormLayout());
				
				formData = new FormData();
				formData.left = new FormAttachment(0, 0);
				formData.top = new FormAttachment(0, 0);
				formData.right = new FormAttachment(100, 0);
				formData.height = 20;
				
				ProgressBar creatureHealthBar = new ProgressBar(composite, SWT.SMOOTH | SWT.HORIZONTAL);
				creatureHealthBar.setLayoutData(formData);
				creatureHealthBar.setMinimum(0);
				creatureHealthBar.setMaximum(Settings.getMaxCharacterHealth());
				
				creatureHealthBars[p][i] = creatureHealthBar;
				
				formData = new FormData();
				formData.left = new FormAttachment(0, 0);
				formData.top = new FormAttachment(creatureHealthBars[p][i], 0, SWT.BOTTOM);
				formData.right = new FormAttachment(100, 0);
				formData.bottom = new FormAttachment(100, 0);
				
				Button creatureButton = new Button(composite, SWT.FLAT);
				creatureButton.setLayoutData(formData);
				creatureButton.addSelectionListener(new CreatureClickListener(p, i));
				
				creatureButtons[p][i] = creatureButton;
			}
			
			if (p == 0)
			{
				new Composite(creatureComposite, SWT.NONE);
			}
		}
		
		////////////////////// Guide label /////////////////////////////
		
		guideLabel = new Label(this, SWT.CENTER);
		
		fd = guideLabel.getFont().getFontData();
		fd[0].setHeight(14);
		guideLabel.setFont(new Font(getDisplay(), fd[0]));
		
		formData = new FormData();
		formData.left = new FormAttachment(0, 0);
		formData.right = new FormAttachment(100, 0);
		formData.bottom = new FormAttachment(creatureComposite, -20, SWT.TOP);
		formData.height = 30;
		
		guideLabel.setLayoutData(formData);
		
		///////////////////////// Board ////////////////////////////////
		
		boardComposite = new Composite(this, SWT.NONE);
		boardComposite.setLayout(new FormLayout());
		
		formData = new FormData();
		formData.left = new FormAttachment(0, 100);
		formData.right = new FormAttachment(100, -100);
		formData.top = new FormAttachment(stopButton, 30, SWT.BOTTOM);
		formData.bottom = new FormAttachment(guideLabel, -20, SWT.TOP);
		
		boardComposite.setLayoutData(formData);
		
		int size = game.getBoard().getSize();
		
		boardButtons = new Control[size][size];
		
		for (int x = 0; x < size; x++)
		{
			for (int y = 0; y < size; y++)
			{
				formData = new FormData();
				formData.left = new FormAttachment((100 * x) / size, 0);
				formData.right = new FormAttachment((100 * (x + 1)) / size, 0);
				formData.top = new FormAttachment((100 * y) / size, 0);
				formData.bottom = new FormAttachment((100 * (y + 1)) / size, 0);
				
				Control boardButton = new Label(boardComposite, SWT.CENTER | SWT.BORDER);
				boardButton.setLayoutData(formData);
				boardButton.addMouseListener(new CellClickListener(x, y));
				
				boardButtons[x][y] = boardButton;
			}
		}
		
		refreshGameState();
	}
	
	/**
	 * Refreshes the UI and syncs it with the current game status, including
	 * board status, players, creatures, and current turn state.
	 * The method also checks if the game is over and returns a value accordingly.
	 * 
	 * @return true if the game is over; false otherwise.
	 */
	private boolean refreshGameState()
	{
		player1Label.setText(game.getPlayer1().getName());
		player2Label.setText(game.getPlayer2().getName());
		
		int armySize = Settings.getArmySize();
		
		for (int p = 0; p < 2; p++)
		{
			for (int i = 0; i < armySize; i++)
			{
				Player player = (p == 0 ? game.getPlayer1() : game.getPlayer2());
				Character ch = player.getArmy()[i];
				
				creatureHealthBars[p][i].setSelection(ch.getHealth());
				
				creatureButtons[p][i].setText(ch.getName());
				creatureButtons[p][i].setEnabled(!ch.isDead());
			}
		}
		
		Board board = game.getBoard();
		int size = board.getSize();
		
		for (int x = 0; x < size; x++)
		{
			for (int y = 0; y < size; y++)
			{
				switch (board.getCellStatus(x, y))
				{
				case Player1:	boardButtons[x][y].setBackground(new Color(SWTUtil.getDisplay(), 0, 0, 255)); break;
				case Player2:	boardButtons[x][y].setBackground(new Color(SWTUtil.getDisplay(), 255, 0, 0)); break;
				
				default:		boardButtons[x][y].setBackground(new Color(SWTUtil.getDisplay(), 255, 255, 255)); break;
				}
			}
		}
		
		refreshGuide();
		
		return checkGameOver();
	}
	
	/**
	 * Returns the text to be displayed in the guide label (and error messages).
	 * The returned text describes to the players what their next moves should be.
	 * 
	 * @return The appropriate guide text
	 */
	private String getGuideText()
	{
		switch (turnState)
		{
		case 0: return game.getCurrentPlayer().getName() + ", please select your attacker.";
		case 1: return game.getIdlePlayer().getName() + ", please select the defender.";
		case 2: return game.getCurrentPlayer().getName() + ", please select your attacking position.";
		case 3: return game.getIdlePlayer().getName() + ", please select the defending position.";
		
		default: return "";
		}
	}
	
	/**
	 * Refreshes the guide label with the guide text.
	 */
	private void refreshGuide()
	{
		guideLabel.setText(getGuideText());
	}
	
	/**
	 * Displays a message box with the guide text.
	 */
	private void showCurrentGuideMessage()
	{
		MessageBox mb = new MessageBox(this);
		
		mb.setText("Error");
		mb.setMessage(getGuideText());
		mb.open();
	}
	
	/**
	 * Advances the current turn, and saves the state of the game.
	 */
	private void advanceTurn()
	{
		if (turnState == 3)
		{
			game.fight(game.getCurrentPlayer(), attacker,
					game.getIdlePlayer(), defender,
					attackX, attackY, defendX, defendY);
			
			game.switchCurrentPlayer();
			
			try
			{
				game.saveState();
			}
			catch (GameException e)
			{
				MessageBox mb = new MessageBox(this);
				
				mb.setText("Error");
				mb.setMessage("Unable to save the state of the game.");
				mb.open();
			}
			
			if (refreshGameState())
			{
				stopGame();
				return;
			}
		}
		
		turnState = (turnState + 1) % 4;
		
		refreshGuide();
	}
	
	/**
	 * Checks whether the game is over and returns a value accordingly.
	 * A game is over when his opponent's creatures are all dead, or when he has conquered
	 * all his territories.
	 * 
	 * @return true if the game is over; false otherwise
	 */
	private boolean checkGameOver()
	{
		GameOverStatus status = game.getGameOverStatus();
		
		String message = null;
		
		switch (status)
		{
		case Player1:	message = game.getPlayer1().getName() + " is the winner!"; break;
		case Player2:	message = game.getPlayer2().getName() + " is the winner!"; break;
		case Tie:		message = "The game ended with a tie!"; break;
		
		default:
			return false;
		}
		
		MessageBox mb = new MessageBox(this);
		
		mb.setText("Game Over");
		mb.setMessage(message);
		mb.open();
		
		return true;
	}
	
	/**
	 * Called when a creature button is clicked.
	 * Sets the creature as the attacker/defender if the turn requires it. Then the turn is advanced.
	 * 
	 * @param player The player for which the creature was clicked (0 for first player, 1 for second player)
	 * @param creature The index of the creature that was clicked
	 */
	private void onCreatureClicked(int player, int creature)
	{
		Player p = (player == 0 ? game.getPlayer1() : game.getPlayer2());
		Character c = p.getArmy()[creature];
		
		if ((turnState == 0) && (p == game.getCurrentPlayer()))
		{
			attacker = c;
			
			advanceTurn();
		}
		else if ((turnState == 1) && (p == game.getIdlePlayer()))
		{
			defender = c;
			
			advanceTurn();
		}
		else
		{
			showCurrentGuideMessage();
		}
	}
	
	/**
	 * Called when a board cell is clicked.
	 * If the turn requires so, the attack/defend position is set the turn is advanced.
	 * 
	 * @param x The x coordinate of the cell
	 * @param y The y coordinate of the cell
	 */
	private void onCellClicked(int x, int y)
	{
		CellStatus cellStatus = game.getBoard().getCellStatus(x, y);
		CellStatus curPlayerStatus = (game.getCurrentPlayerStatus() == CurrentPlayerStatus.Player1 ?
				CellStatus.Player1 : CellStatus.Player2);
		
		if ((turnState == 2) && (cellStatus == curPlayerStatus))
		{
			attackX = x;
			attackY = y;

			advanceTurn();
		}
		else if ((turnState == 3) && (cellStatus != curPlayerStatus))
		{
			defendX = x;
			defendY = y;
			
			advanceTurn();
		}
		else
		{
			showCurrentGuideMessage();
		}
	}
	
	/**
	 * Class to respond to creature clicks.
	 */
	private class CreatureClickListener extends SelectionAdapter
	{
		private int player;
		private int creature;
		
		public CreatureClickListener(int player, int creature)
		{
			this.player = player;
			this.creature = creature;
		}
		
		@Override
		public void widgetSelected(SelectionEvent e)
		{
			onCreatureClicked(player, creature);
		}
	}
	
	/**
	 * Class to respond to board cell clicks.
	 */
	private class CellClickListener extends MouseAdapter
	{
		private int x;
		private int y;
		
		public CellClickListener(int x, int y)
		{
			this.x = x;
			this.y = y;
		}
		
		@Override
		public void mouseDown(MouseEvent e)
		{
			onCellClicked(x, y);
		}
	}
	
	/**
	 * Requests to stop the game.
	 */
	private void stopGame()
	{
		callback.onGameStopped(this);
	}
}
